package com.liming.stock.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.liming.common.constant.DBConstants;
import com.liming.common.constant.SystemConstants;
import com.liming.common.enums.StockFlowEnum;
import com.liming.common.enums.StockLockStatusEnum;
import com.liming.common.enums.StockStatusEnum;
import com.liming.common.exception.BusinessException;
import com.liming.common.pojo.dto.stock.*;
import com.liming.common.pojo.entity.stock.Stock;
import com.liming.common.pojo.entity.stock.StockFlow;
import com.liming.common.pojo.entity.stock.StockLock;
import com.liming.common.pojo.vo.PageVO;
import com.liming.common.pojo.vo.product.BaseProductVO;
import com.liming.common.pojo.vo.product.openfeign.FeignProductVO;
import com.liming.common.pojo.vo.stock.StockVO;
import com.liming.common.util.PageUtils;
import com.liming.common.util.ThreadLocalUtils;
import com.liming.common.util.mybatisplus.MPUtil;
import com.liming.product.client.ProductClient;
import com.liming.stock.mapper.StockMircoMapper;
import com.liming.stock.service.StockFlowMircoService;
import com.liming.stock.service.StockLockMircoService;
import com.liming.stock.service.StockMircoService;
import com.liming.stock.service.WarehouseMircoService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

/**
 * @author liming
 * @date 2025/1/21
 */
@Slf4j
@Service
@AllArgsConstructor
public class StockMircoServiceImpl extends ServiceImpl<StockMircoMapper, Stock>
        implements StockMircoService, MPUtil<Stock> {

    private final StockFlowMircoService stockFlowService;
    private final WarehouseMircoService warehouseService;
    private final StockLockMircoService stockLockMircoService;
    private final ProductClient productClient;

    @Override
    public PageVO<StockVO> page(StockPageQuery query) {
        // TODO 查询商品
        List<Long> wrapper = productClient.productIdsByStockQuery(query);
        if (CollUtil.isEmpty(wrapper)) {
            return PageUtils.emptyPage();
        }
        IPage<Stock> page = lambdaQuery()
                .in(Stock::getProductId, wrapper)
                .eq(ObjectUtil.isNotNull(query.getStatus()), Stock::getStatus, query.getStatus())
                .page(setPage(query));
        if (CollUtil.isEmpty(page.getRecords())) {
            return PageUtils.emptyPage();
        }
        // 所有库存的商品id
        List<Long> productIds = page.getRecords().stream().map(Stock::getProductId).toList();
        // 所有库存对应的商品信息
        Map<Long, FeignProductVO> productMap = productClient.listByIds(productIds)
                .stream()
                .collect(Collectors.toMap(BaseProductVO::getId, feignProductVO -> feignProductVO));
        return PageUtils.convert(page, stock -> {
            FeignProductVO productVO = productMap.get(stock.getProductId());
            StockVO stockVO = new StockVO();
            BeanUtils.copyProperties(stock, stockVO);
            stockVO.setProductName(productVO.getName());
            stockVO.setModelNumber(productVO.getModelNumber());
            stockVO.setWarehouseName(warehouseService.getById(stock.getWarehouseId()).getName());
            return stockVO;
        });
    }

    @Override
    public Boolean initStock(InitStockDTO dto) {
        Stock stock = new Stock();
        BeanUtils.copyProperties(dto, stock);
        // 默认仓库
        dto.setWarehouseId(DBConstants.WAREHOUSE_DEFAULT_ID);
        // 新增库存时，可用库存就为总库存
        stock.setAvailableStock(dto.getTotalStock());
        Assert.isTrue(save(stock), () -> new BusinessException("初始化库存保存失败 - 初始化"));
        // 新增库存流水
        CompletableFuture.runAsync(() -> {
            AddStockFlowDTO flowDTO = new AddStockFlowDTO();
            flowDTO.setStockId(stock.getId());
            flowDTO.setProductId(stock.getProductId());
            // 入库
            flowDTO.setType(StockFlowEnum.IN.getCode());
            flowDTO.setQuantity(stock.getTotalStock());
            flowDTO.setOperatorId(dto.getOperatorId());
            flowDTO.setRemark("新增库存 - 管理员");
            Assert.isTrue(stockFlowService.adminStockFlow(flowDTO), () -> new BusinessException("库存流水保存失败 - 初始化"));
        });
        return true;
    }

    @Override
    public Integer getAvailableQuantity(Long productId) {
        List<Stock> productsInWarehouse = lambdaQuery().eq(Stock::getProductId, productId).list();
        Assert.notEmpty(productsInWarehouse, () -> new BusinessException("商品库存不存在 - 请添加商品库存"));
        return productsInWarehouse.stream().mapToInt(Stock::getAvailableStock).sum();
    }

    @Override
    public Boolean updStockQuantity(UpdStockQuantityDTO dto) {
        Stock stock = getById(dto.getId());
        Integer oldTotalStock = stock.getTotalStock();
        Assert.notNull(stock, () -> new BusinessException("库存不存在"));
        Integer lockedStock = stock.getLockedStock();
        Integer totalStock = dto.getTotalStock();
        Assert.isTrue(totalStock >= lockedStock, () -> new BusinessException("库存数量 必须大于等于 锁定库存"));
        Integer alertStock = dto.getAlertStock();
        Integer availableStock = totalStock - lockedStock;
        stock.setTotalStock(totalStock);
        stock.setAlertStock(alertStock);
        stock.setAvailableStock(availableStock);
        Assert.isTrue(updateById(stock), () -> new BusinessException("库存数量修改失败"));
        // 判断 修改的总库存数量 是比之前多了 还是 少了 ？ 新增入库流水 ： 新增出库流水
        if (Objects.equals(oldTotalStock, dto.getTotalStock())) {
            // 相等不变
        } else if (dto.getTotalStock() > oldTotalStock) {
            // 多了 入库流水
            int quantity = dto.getTotalStock() - oldTotalStock;
            AddStockFlowDTO inFlow = new AddStockFlowDTO();
            inFlow.setStockId(stock.getId());
            inFlow.setProductId(stock.getProductId());
            inFlow.setType(StockFlowEnum.IN.getCode());
            inFlow.setQuantity(quantity);
            inFlow.setOperatorId(ThreadLocalUtils.get(SystemConstants.ID));
            inFlow.setRemark("新增库存 - 管理员");
            stockFlowService.adminStockFlow(inFlow);
        } else {
            // 少了
            int quantity = oldTotalStock - dto.getTotalStock();
            AddStockFlowDTO outFlow = new AddStockFlowDTO();
            outFlow.setStockId(stock.getId());
            outFlow.setProductId(stock.getProductId());
            outFlow.setType(StockFlowEnum.OUT.getCode());
            outFlow.setQuantity(quantity);
            outFlow.setOperatorId(ThreadLocalUtils.get(SystemConstants.ID));
            outFlow.setRemark("出库 - 管理员");
            stockFlowService.adminStockFlow(outFlow);
        }

        return true;
    }

    @Override
    public Boolean updStatus(Long id) {
        String operatorId = ThreadLocalUtils.get(SystemConstants.ID);
        Stock stock = getById(id);
        Assert.notNull(stock, () -> new BusinessException("库存不存在"));
        Integer status = stock.getStatus();
        if (ObjectUtil.equal(status, StockStatusEnum.DISABLE.getCode())) {
            stock.setStatus(StockStatusEnum.ENABLE.getCode());
            Assert.isTrue(updateById(stock), () -> new BusinessException("库存状态修改失败"));
            CompletableFuture.runAsync(() -> {
                // 库存下架后，库存流水状态 为 库存解锁
                AddStockFlowDTO add = new AddStockFlowDTO();
                add.setStockId(stock.getId());
                add.setProductId(stock.getProductId());
                add.setType(StockFlowEnum.UNLOCK.getCode());
                add.setQuantity(stock.getAvailableStock());
                add.setOperatorId(operatorId);
                add.setRemark("库存上架 - 管理员");
                stockFlowService.adminStockFlow(add);
            });
            return true;
        }
        stock.setStatus(StockStatusEnum.DISABLE.getCode());
        Assert.isTrue(updateById(stock), () -> new BusinessException("库存状态修改失败"));
        CompletableFuture.runAsync(() -> {
            // 库存下架后，库存流水状态 为 库存锁定
            AddStockFlowDTO add = new AddStockFlowDTO();
            add.setStockId(stock.getId());
            add.setProductId(stock.getProductId());
            add.setType(StockFlowEnum.LOCK.getCode());
            add.setQuantity(stock.getAvailableStock());
            add.setOperatorId(operatorId);
            add.setRemark("库存下架 - 管理员");
            stockFlowService.adminStockFlow(add);
        });
        return true;
    }

    @Override
    public Boolean out(Long id, Integer quantity) {
        if (quantity <= 0) {
            quantity = 1;
        }
        String operatorId = ThreadLocalUtils.get(SystemConstants.ID);
        Stock stock = getById(id);

        Assert.notNull(stock, () -> new BusinessException("库存不存在"));
        Assert.isTrue(ObjectUtil.equal(stock.getStatus(), StockStatusEnum.ENABLE.getCode()),
                () -> new BusinessException("库存状态为下架，不能出库"));
        Assert.isTrue(quantity <= stock.getAvailableStock(), () -> new BusinessException("库存数量不足"));
        stock.setTotalStock(stock.getTotalStock() - quantity);
        stock.setAvailableStock(stock.getTotalStock() - stock.getLockedStock());
        Assert.isTrue(updateById(stock), () -> new BusinessException("库存出库失败"));

        // 新增库存流水
        final Integer finalQuantity = quantity;
        CompletableFuture.runAsync(() -> {
            AddStockFlowDTO flowDTO = new AddStockFlowDTO();
            flowDTO.setStockId(stock.getId());
            flowDTO.setProductId(stock.getProductId());
            // 出库
            flowDTO.setType(StockFlowEnum.OUT.getCode());
            flowDTO.setQuantity(finalQuantity);
            flowDTO.setOperatorId(operatorId);
            flowDTO.setRemark("出库 - 管理员");
            Assert.isTrue(stockFlowService.adminStockFlow(flowDTO), () -> new BusinessException("库存流水保存失败 - 出库"));
        });
        return true;
    }

    @Override
    public Long alertStockCount() {
        return lambdaQuery().list().stream().filter(stock -> stock.getAvailableStock() <= stock.getAlertStock()).count();
    }

    @Override
    public Map<Long, Integer> allProductAvailableQuantityMap() {
        return lambdaQuery().list().stream().collect(Collectors.toMap(Stock::getProductId, Stock::getAvailableStock));
    }

    @Override
    public Boolean lockStockByProductIdAndQuantity(Long orderId, Map<Long, Integer> productIdAndQuantityMap) {
        // 锁定库存
        // 商品是否还有剩余库存可以锁定？
        // 剩余库存（可用库存）

        // 查询所有商品的所有库存信息
        Map<Long, Map<Long, Stock>> productIdAndWarehouseIdAndAvailableNum = lambdaQuery()
                .in(Stock::getProductId, productIdAndQuantityMap.keySet())
                .list()
                .stream()
                .collect(Collectors.groupingBy(Stock::getProductId, Collectors.toMap(Stock::getWarehouseId, stock -> stock)));
        for (Map.Entry<Long, Map<Long, Stock>> entry : productIdAndWarehouseIdAndAvailableNum.entrySet()) {
            Long productId = entry.getKey();
            Map<Long, Stock> warehouseIdAndQuantity = entry.getValue();
            // 遍历仓库 ， 选一个可以扣减的 。 如果一个都没有 抛出异常
            boolean flag = false;
            for (Map.Entry<Long, Stock> warehouseAvailableNumEntry : warehouseIdAndQuantity.entrySet()) {
                // 如果扣减成功 就跳出循环
                // 可用数量
                Stock stock = warehouseAvailableNumEntry.getValue();
                Integer availableQuantity = stock.getAvailableStock();
                // 目前要扣减的数量
                Integer quantity = productIdAndQuantityMap.get(productId);
                if (availableQuantity >= quantity) {
                    // 锁定库存数量
                    flag = true;
                    boolean update = lambdaUpdate()
                            .setSql("available_stock = available_stock - " + quantity)
                            .setSql("locked_stock = locked_stock + " + quantity)
                            .eq(Stock::getProductId, productId)
                            .eq(Stock::getWarehouseId, warehouseAvailableNumEntry.getKey())
                            .update();
                    Assert.isTrue(update, () -> new BusinessException("库存扣减失败"));
                    // 库存流水
                    String strUserId = ThreadLocalUtils.get(SystemConstants.ID);
                    CompletableFuture.runAsync(() -> {
                        UserOrderStockFlowDTO dto = new UserOrderStockFlowDTO();
                        dto.setUserId(strUserId);
                        List<UserOrderProductListDTO> userOrderProductListDTOS = productIdAndQuantityMap.entrySet().stream().map(productIdNum -> {
                            UserOrderProductListDTO product = new UserOrderProductListDTO();
                            product.setStockId(stock.getId());
                            product.setProductId(productIdNum.getKey());
                            product.setQuantity(productIdNum.getValue());
                            return product;
                        }).toList();
                        dto.setProductList(userOrderProductListDTOS);
                        dto.setOrderId(orderId);
                        dto.setRemark("用户下单 - 库存锁定");
                        stockFlowService.userOrderLockStockFlow(dto);
                    });

                    // 库存锁定流水 （后期 支付失败 可以根据该流水回滚）
                    CompletableFuture.runAsync(() -> {
                        List<StockLock> stockLockList = productIdAndQuantityMap.entrySet().stream().map(productIdNum -> {
                            StockLock stockLock = new StockLock();
                            stockLock.setStockId(stock.getId());
                            stockLock.setProductId(productIdNum.getKey());
                            stockLock.setOrderId(orderId);
                            stockLock.setQuantity(productIdNum.getValue());
                            stockLock.setStatus(StockLockStatusEnum.LOCKING.getCode());
                            return stockLock;
                        }).toList();
                        stockLockMircoService.saveBatch(stockLockList);
                    });
                }
            }
            // 如果flag 仍然是false ， 说明扣减失败
            if (!flag) {
                throw new BusinessException("商品:" + productId + "库存不足");
            }
        }
        return true;
    }

    @Override
    @Transactional
    public Boolean paymentSuccess(Long orderId) {
        List<StockLock> lockingList = stockLockMircoService.stockLockListByOrderIdAndStatus(orderId, StockLockStatusEnum.LOCKING.getCode());
        for (StockLock stockLock : lockingList) {
            Long stockId = stockLock.getStockId();
            Integer quantity = stockLock.getQuantity();
            boolean update = lambdaUpdate()
                    .eq(Stock::getId, stockId)
                    .setSql("locked_stock = locked_stock - " + quantity)
                    .setSql("total_stock = total_stock - " + quantity)
                    .update();
            Assert.isTrue(update, () -> new BusinessException("支付成功 - 库存锁定扣减失败"));
        }
        stockFlowService.userOrderOutStockFLow(orderId);
        for (StockLock stockLock : lockingList) {
            // 扣减
            stockLock.setStatus(StockLockStatusEnum.DEDUCT.getCode());
        }
        stockLockMircoService.updateBatchById(lockingList);
        return true;
    }

    @Override
    @Transactional
    public Boolean paymentFailure(Long orderId) {
        List<StockLock> lockingList = stockLockMircoService.stockLockListByOrderIdAndStatus(orderId, StockLockStatusEnum.LOCKING.getCode());
        for (StockLock stockLock : lockingList) {
            Long stockId = stockLock.getStockId();
            Integer quantity = stockLock.getQuantity();
            boolean update = lambdaUpdate()
                    .eq(Stock::getId, stockId)
                    .setSql("locked_stock = locked_stock - " + quantity)
                    .setSql("available_stock = available_stock + " + quantity)
                    .update();
            Assert.isTrue(update, () -> new BusinessException("支付失败 - 库存锁定|可用恢复 失败"));
        }
        stockFlowService.userOrderUnlockStockFLow(orderId);
        for (StockLock stockLock : lockingList) {
            // 释放
            stockLock.setStatus(StockLockStatusEnum.RELEASE.getCode());
        }
        stockLockMircoService.updateBatchById(lockingList);
        return true;
    }

    @Override
    public void refundSuccess(Long orderId) {
        // 查询订单流水 (什么订单可以退款？ 已支付、待发货、待自提) 它们都有出库库存流水
        // 并新增退款流水
        List<StockFlow> outStockFlowList = stockFlowService.addRefundFlowBecauseRefundSuccess(orderId);
        // 根据出库流水 新增库存流水
        for (StockFlow flow : outStockFlowList) {
            Long stockId = flow.getStockId();
            Integer quantity = flow.getQuantity();
            boolean update = lambdaUpdate()
                    .eq(Stock::getId, stockId)
                    .setSql("available_stock = available_stock + " + quantity)
                    .setSql("total_stock = total_stock + " + quantity)
                    .update();
            Assert.isTrue(update, () -> new BusinessException("退款成功 - 库存恢复 失败"));
        }
    }
}
